1 /*
2  * The MIT License (MIT)
3  *
4  * Copyright (c) 2014 Devisualization (Richard Andrew Cattermole)
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in all
14  * copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22  * SOFTWARE.
23  */
24 module devisualization.window.window;
25 import devisualization.window.interfaces.window;
26 public import devisualization.window.interfaces.window : WindowConfig, Windowable;
27 public import devisualization.window.interfaces.events : MouseButtons, Keys, KeyModifiers;
28 public import devisualization.window.interfaces.context : WindowContextType, IContext;
29 public import devisualization.window.context;
30 import devisualization.window.interfaces.eventable;
31 import std.conv : to;
32 
33 class Window : Windowable {
34     private {
35         HWND hwnd_;
36         HICON previousIcon_;
37         wstring lastTitle;
38         bool hasBeenClosed_;
39 
40         WindowConfig config_;
41         IContext context_ = null;
42     }
43 
44     this(T...)(T config) { this(WindowConfig(config)); } 
45 
46     this(WindowConfig config) {
47         config_ = config;
48         title = config.title;
49 		
50 		Window this_ = this;
51         hwnd_ = createWindow(config.x, config.y, config.width, config.height, &windowHandler, &this_);
52     }
53 
54     static {
55         void messageLoop() {
56             import core.thread : Thread;
57             import core.time : dur;
58             
59             while(true) {
60 				while(Window.messageLoopIteration()) {}
61                 Thread.sleep(dur!"msecs"(50));
62             }
63         }
64 
65         bool messageLoopIteration(uint minBlocking = 0, uint maxNonBlocking = 1) {
66 			MSG msg;
67 			uint i;
68 			
69 			while (i < minBlocking) {
70                 int ret = GetMessageW(&msg, null, 0, 0);
71 
72                 TranslateMessage(&msg);
73                 DispatchMessageW(&msg);
74                 
75                 i++;
76             }
77             
78             i = 0;
79             while (i < maxNonBlocking) {
80                 int ret = PeekMessageW(&msg, null, 0, 0, PM_REMOVE);
81 
82                 if (ret == 0) {
83                     return false;
84                 } else {
85                     TranslateMessage(&msg);
86                     DispatchMessageW(&msg);
87                 }
88                 
89                 i++;
90             }
91 
92 			return true;
93         }
94     }
95     
96     @property {
97         HWND hwnd()
98         in {
99             assert(!hasBeenClosed_);
100         } body {
101             return hwnd_;
102         }
103         
104         void title(string value)
105         in {
106             assert(!hasBeenClosed_);
107         } body {
108             title(to!wstring(value));
109         }
110         
111         void title(dstring value)
112         in {
113             assert(!hasBeenClosed_);
114         } body {
115             title(to!wstring(value));
116         }
117         
118         void title(wstring value)
119         in {
120             assert(!hasBeenClosed_);
121         } body {
122             if (value != ""w && value[$-1] != '\0')
123                 value ~= '\0';
124 			else if (value == ""w)
125 				value = "\0"w;
126             lastTitle = value;
127             
128             SetWindowTextW(hwnd_, cast(ushort*)lastTitle.ptr);
129         }
130         
131         void size(uint width, uint height)
132         in {
133             assert(!hasBeenClosed_);
134         } body {
135             // calculates correct width/height size of window
136             RECT rect;
137             
138             rect.top = 0;
139             rect.left = 0;
140             rect.right = width;
141             rect.bottom = height;
142             
143             auto style = GetWindowLongW(hwnd_, GWL_STYLE);
144             if (style == dwFullscreen) {
145                 SetWindowLongW(hwnd_, GWL_STYLE, dwStyle);
146                 SetWindowLongW(hwnd_, GWL_EXSTYLE, dwExStyle);
147             }
148             
149             AdjustWindowRectEx(&rect, dwStyle, false, dwExStyle);
150             width = rect.right;
151             height = rect.bottom;
152             // calculates correct width/height size of window
153             
154             SetWindowPos(hwnd_, null, 0, 0, width, height, SWP_NOMOVE | SWP_NOOWNERZORDER);
155         }
156         
157         void move(int x, int y)
158         in {
159             assert(!hasBeenClosed_);
160         } body {
161             SetWindowPos(hwnd_, null, x, y, 0, 0, SWP_NOSIZE | SWP_NOOWNERZORDER);
162         }
163         
164         void canResize(bool can = true)
165         in {
166             assert(!hasBeenClosed_);
167         } body {
168             auto style = GetWindowLongW(hwnd_, GWL_STYLE);
169             if (can)
170                 style |= WS_SIZEBOX | WS_MAXIMIZEBOX;
171             else
172                 style &= ~(WS_SIZEBOX | WS_MAXIMIZEBOX);
173             SetWindowLongW(hwnd_, GWL_STYLE, style);
174         }
175         
176         void fullscreen(bool isfs = true)
177         in {
178             assert(!hasBeenClosed_);
179         } body {
180             if (isfs) {
181                 SetWindowLongW(hwnd_, GWL_STYLE, dwFullscreen);
182                 SetWindowLongW(hwnd_, GWL_EXSTYLE, dwExFullscreen);
183                 
184                 int cx = GetSystemMetrics(SM_CXSCREEN);
185                 int cy = GetSystemMetrics(SM_CYSCREEN);
186                 SetWindowPos(hwnd_, HWND_TOP, 0, 0, cx, cy, SWP_SHOWWINDOW);
187             } else {
188                 canResize = true;
189             }
190         }
191 
192         void close()
193         in {
194             assert(!hasBeenClosed_);
195         } body {
196             DestroyWindow(hwnd_);
197             CloseWindow(hwnd_);
198 			hasBeenClosed_ = true;
199         }
200 
201         IContext context()
202         in {
203             assert(!hasBeenClosed_);
204         } body {
205             return context_;
206         }
207     }
208     
209     override {
210         void show()
211         in {
212             assert(!hasBeenClosed_);
213         } body {
214             ShowWindow(hwnd_, SW_SHOW);
215         }
216         
217         void hide()
218         in {
219             assert(!hasBeenClosed_);
220         } body {
221             ShowWindow(hwnd_, SW_HIDE);
222         }
223 
224         void icon(Image image)
225         in {
226             assert(!hasBeenClosed_);
227             assert(image !is null);
228         } body {
229             ubyte[4][] data;
230             foreach(pixel; image.rgba.allPixels) {
231                 data ~= [pixel.b_ubyte, pixel.g_ubyte, pixel.r_ubyte, pixel.a_ubyte];
232             }
233 
234             HBITMAP bitmap = CreateBitmap(cast(uint)image.width, cast(uint)image.height, 1, 32, data.ptr);
235             HBITMAP hbmMask = CreateCompatibleBitmap(GetDC(hwnd_), cast(uint)image.width, cast(uint)image.height);
236             
237             ICONINFO ii;
238             ii.fIcon = TRUE;
239             ii.hbmColor = bitmap;
240             ii.hbmMask = hbmMask;
241             
242             HICON hIcon = CreateIconIndirect(&ii);
243             DeleteObject(hbmMask);
244             
245             if (hIcon) {
246                 SendMessageW(hwnd_, WM_SETICON, cast(WPARAM)ICON_BIG, cast(LPARAM)hIcon);
247                 SendMessageW(hwnd_, WM_SETICON, cast(WPARAM)ICON_SMALL, cast(LPARAM)hIcon);
248             }
249         }
250 
251         deprecated("Use Devisualization.Image method instead")
252         void icon(ushort width, ushort height, ubyte[3][] idata, ubyte[3]* transparent = null)
253         in {
254             assert(!hasBeenClosed_);
255             assert(width * height == data.length, "Icon pixels length must be equal to width * height");
256         } body {
257             ubyte[4][] data;
258             foreach(v; idata) {
259                 data ~= [v[2], v[1], v[0], 0];
260             }
261 
262             HBITMAP bitmap = CreateBitmap(width, height, 1, 32, data.ptr);
263             HBITMAP hbmMask;
264             if (transparent is null)
265                 hbmMask = CreateCompatibleBitmap(GetDC(hwnd_), width, height);
266             else {
267                 COLORREF crTransparent = RGB((*transparent)[0], (*transparent)[1], (*transparent)[2]);
268 
269                 HDC hdcMem, hdcMem2;
270                 BITMAP bm;
271 
272                 GetObjectA(bitmap, BITMAP.sizeof, &bm);
273                 hbmMask = CreateBitmap(width, height, 1, 1, null);
274 
275                 hdcMem = CreateCompatibleDC(GetDC(hwnd_));
276                 hdcMem2 = CreateCompatibleDC(GetDC(hwnd_));
277 
278                 SelectObject(hdcMem, bitmap);
279                 SelectObject(hdcMem2, hbmMask);
280 
281                 SetBkColor(hdcMem, crTransparent);
282                 BitBlt(hdcMem2, 0, 0, width, height, hdcMem, 0, 0, SRCCOPY);
283                 BitBlt(hdcMem, 0, 0, width, height, hdcMem2, 0, 0, SRCINVERT);
284 
285                 DeleteDC(hdcMem);
286                 DeleteDC(hdcMem2);
287             }
288 
289             ICONINFO ii;
290             ii.fIcon = TRUE;
291             ii.hbmColor = bitmap;
292             ii.hbmMask = hbmMask;
293 
294             HICON hIcon = CreateIconIndirect(&ii);
295             DeleteObject(hbmMask);
296 
297             if (hIcon) {
298                 SendMessageW(hwnd_, WM_SETICON, cast(WPARAM)ICON_BIG, cast(LPARAM)hIcon);
299                 SendMessageW(hwnd_, WM_SETICON, cast(WPARAM)ICON_SMALL, cast(LPARAM)hIcon);
300             }
301         }
302 
303         bool hasBeenClosed() { return hasBeenClosed_; }
304     }
305     
306     mixin Eventing!("onDraw", Windowable);
307     mixin Eventing!("onMove", Windowable, int, int);
308     mixin Eventing!("onResize", Windowable, uint, uint);
309     mixin Eventing!("onClose", Windowable);
310 
311     mixin Eventing!("onMouseDown", Windowable, MouseButtons, int, int);
312     mixin Eventing!("onMouseMove", Windowable, int, int);
313     mixin Eventing!("onMouseUp", Windowable, MouseButtons);
314     mixin Eventing!("onKeyDown", Windowable, Keys, KeyModifiers);
315     mixin Eventing!("onKeyUp", Windowable, Keys, KeyModifiers);
316 }
317 
318 
319 private {    
320     import windows;
321     
322     enum DWORD dwExStyle = 0;
323     enum DWORD dwStyle = WS_OVERLAPPEDWINDOW;
324     
325     enum wstring WindowClassName = "Devisualization.Window window\0"w;
326     
327     enum DWORD dwFullscreen = WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
328     enum DWORD dwExFullscreen = WS_EX_APPWINDOW | WS_EX_TOPMOST;
329 
330     // wasn't in windows bindings woops
331 
332     enum GCL_HICON = -14;
333     enum SRCCOPY = 0xCC0020;
334     enum SRCINVERT = 0x660046;
335 
336     COLORREF RGB(ubyte r, ubyte g, ubyte b) {
337         r = r & 0xFF;
338         g = g & 0xFF;
339         b = b & 0xFF;
340         return cast(COLORREF)((b << 16) | (g << 8) | r);
341     }
342 
343 
344     // more actual code
345     
346     HWND createWindow(int x, int y, uint width, uint height, WindowProc wProc, Window* windowPtr) {
347         static HINSTANCE hInstance;
348         
349         if (hInstance is HINSTANCE.init)
350             hInstance = GetModuleHandleA(null);
351         
352         WNDCLASSW wc;
353         wc.lpfnWndProc = wProc;
354         wc.hInstance = hInstance;
355         wc.lpszClassName = cast(ushort*)WindowClassName.ptr;
356         wc.hCursor = LoadCursorW(null, cast(ushort*)IDC_ARROW);
357         wc.style |= CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
358         if (!RegisterClassW(&wc))
359             throw new WindowNotCreatable();
360         
361         // calculates correct width/height size of window
362         RECT rect;
363         
364         rect.top = 0;
365         rect.left = 0;
366         rect.right = width;
367         rect.bottom = height;
368         
369         AdjustWindowRectEx(&rect, dwStyle, false, dwExStyle);
370         width = rect.right;
371         height = rect.bottom;
372         // calculates correct width/height size of window
373         
374         HWND hwnd = CreateWindowExW(
375             dwExStyle,
376             cast(ushort*)WindowClassName.ptr,
377             null,
378             dwStyle,
379             x, y,
380             width, height,
381             null,
382             null,
383             hInstance,
384             windowPtr);
385         
386         if (hwnd is null)
387             throw new WindowNotCreatable();
388 
389         InvalidateRgn(hwnd, null, true);        
390 
391         return hwnd;
392     }
393     
394     extern(Windows) {
395         alias WindowProc = LRESULT function(HWND hwnd, uint uMsg, WPARAM wParam, LPARAM lParam);
396         
397         LRESULT windowHandler(HWND hwnd, uint uMsg, WPARAM wParam, LPARAM lParam) {
398             /*
399              * Handles creational arguments aka the current window 
400              */
401             Window window;
402             switch(uMsg) {
403                 case WM_CREATE:
404                     CREATESTRUCTW pCreate = *cast(CREATESTRUCTW*)lParam;
405                     void* pState = pCreate.lpCreateParams;
406                     
407                     version(X86_64) {
408                         SetWindowLongPtrW(hwnd, GWLP_USERDATA, *cast(ulong*)pState);
409                     } else {
410                         SetWindowLongW(hwnd, GWLP_USERDATA, *cast(uint*)pState);
411                     }
412 
413                     return cast(LRESULT)0;
414                     
415                 default:
416                     version(X86_64) {
417                         window = cast(Window)cast(ulong*)GetWindowLongPtrW(hwnd, GWLP_USERDATA);
418                     } else {
419                         window = cast(Window)cast(uint*)GetWindowLongW(hwnd, GWLP_USERDATA);
420                     }
421             }
422             
423             /*
424              * Normal flow handling 
425              */
426             switch(uMsg) {
427                 case WM_DESTROY:
428                     return cast(LRESULT)0;
429                     
430                 case WM_PAINT:
431                     if (window.context_ is null) {
432 						if ((window.config_.contextType | WindowContextType.Opengl3Plus) == WindowContextType.Opengl3Plus || (window.config_.contextType | WindowContextType.OpenglLegacy) == WindowContextType.OpenglLegacy) {
433                             window.context_ = new OpenglContext(window, window.config_);
434                         } else if (window.config_.contextType == WindowContextType.Direct3D) {
435 							version(Have_directx_d) {
436 								window.context_ = new Direct3dContext(window, window.config_);
437 							}
438 						} else if (window.config_.contextType == WindowContextType.Buffer2D) {
439 							window.context_ = new Buffer2DContext(window, window.config_);
440 						}
441                     }
442                     window.onDraw();
443                     return cast(LRESULT)0;
444                     
445                 case WM_SIZE:
446                     window.onResize(LOWORD(lParam), HIWORD(lParam));
447                     return cast(LRESULT)0;
448                     
449                 case WM_MOVE:
450                     window.onMove(LOWORD(lParam), HIWORD(lParam));
451                     return cast(LRESULT)0;
452                     
453                 case WM_LBUTTONDOWN:
454                     window.onMouseDown(MouseButtons.Left, LOWORD(lParam), HIWORD(lParam));
455                     return cast(LRESULT)0;
456                 case WM_MBUTTONDOWN:
457                     window.onMouseDown(MouseButtons.Middle, LOWORD(lParam), HIWORD(lParam));
458                     return cast(LRESULT)0;
459                 case WM_RBUTTONDOWN:
460                     window.onMouseDown(MouseButtons.Right, LOWORD(lParam), HIWORD(lParam));
461                     return cast(LRESULT)0;
462 
463                 case WM_MOUSEMOVE:
464                     window.onMouseMove(LOWORD(lParam), HIWORD(lParam));
465                     return cast(LRESULT)0;
466 
467                 case WM_LBUTTONUP:
468                     window.onMouseUp(MouseButtons.Left);
469                     return cast(LRESULT)0;
470                 case WM_MBUTTONUP:
471                     window.onMouseUp(MouseButtons.Middle);
472                     return cast(LRESULT)0;
473                 case WM_RBUTTONUP:
474                     window.onMouseUp(MouseButtons.Right);
475                     return cast(LRESULT)0;
476                    
477                 case WM_SYSKEYDOWN:
478                 case WM_KEYDOWN:
479                     window.onKeyDown(convertWinKeys(cast(uint)wParam), convertWinKeyModifiers());
480                     return cast(LRESULT)0;
481 
482                 case WM_SYSKEYUP:
483                 case WM_KEYUP:
484                     window.onKeyUp(convertWinKeys(cast(uint)wParam), convertWinKeyModifiers());
485                     return cast(LRESULT)0;
486                 case WM_CLOSE:
487                     window.onClose();
488                     if (window.countOnClose() == 0)
489                         window.close();
490                     return cast(LRESULT)0;
491 
492                 default:
493                     break;
494             }
495             return DefWindowProcW(hwnd, uMsg, wParam, lParam);
496         }
497     }
498 
499     Keys convertWinKeys(uint code) {
500         switch (code)
501         {
502             case VK_OEM_1: return Keys.Semicolon;
503             case VK_OEM_2: return Keys.Slash;
504             case VK_OEM_PLUS: return Keys.Equals;
505             case VK_OEM_MINUS: return Keys.Hyphen;
506             case VK_OEM_4: return Keys.LeftBracket;
507             case VK_OEM_6: return Keys.RightBracket;
508             case VK_OEM_COMMA: return Keys.Comma;
509             case VK_OEM_PERIOD: return Keys.Period;
510             case VK_OEM_7: return Keys.Quote;
511             case VK_OEM_5: return Keys.Backslash;
512             case VK_OEM_3: return Keys.Tilde;
513             case VK_ESCAPE: return Keys.Escape;
514             case VK_SPACE: return Keys.Space;
515             case VK_RETURN: return Keys.Enter;
516             case VK_BACK: return Keys.Backspace;
517             case VK_TAB: return Keys.Tab;
518             case VK_PRIOR: return Keys.PageUp;
519             case VK_NEXT: return Keys.PageDown;
520             case VK_END: return Keys.End;
521             case VK_HOME: return Keys.Home;
522             case VK_INSERT: return Keys.Insert;
523             case VK_DELETE: return Keys.Delete;
524             case VK_ADD: return Keys.Add;
525             case VK_SUBTRACT: return Keys.Subtract;
526             case VK_MULTIPLY: return Keys.Multiply;
527             case VK_DIVIDE: return Keys.Divide;
528             case VK_PAUSE: return Keys.Pause;
529             case VK_LEFT: return Keys.Left;
530             case VK_RIGHT: return Keys.Right;
531             case VK_UP: return Keys.Up;
532             case VK_DOWN: return Keys.Down;
533                 
534             default:
535                 if (code >= VK_F1 && code <= VK_F12)
536                     return cast(Keys)(Keys.F1 + code - VK_F1);
537                 else if (code >= VK_NUMPAD0 && code <= VK_NUMPAD9)
538                     return cast(Keys)(Keys.Numpad0 + code - VK_NUMPAD0);
539                 else if (code >= 'A' && code <= 'Z')
540                     return cast(Keys)(Keys.A + code - 'A');
541                 else if (code >= '0' && code <= '9')
542                     return cast(Keys)(Keys.Number0 + code - '0');
543         }
544         
545         return Keys.Unknown;
546     }
547 
548     KeyModifiers convertWinKeyModifiers() {
549         KeyModifiers ret;
550 
551         if (HIWORD(GetKeyState(VK_MENU)) != 0)
552             ret |= KeyModifiers.Alt;
553         if (HIWORD(GetKeyState(VK_CONTROL)) != 0)
554             ret |= KeyModifiers.Control;
555         if (HIWORD(GetKeyState(VK_SHIFT)) != 0 || LOWORD(GetKeyState(VK_CAPITAL)) != 0)
556             ret |= KeyModifiers.Shift;
557 
558         return ret;
559     }
560 }